package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.search.factory.ScheduleTaskThreadFactory;
import com.atguigu.tingshu.search.repository.AlbumRepository;
import com.atguigu.tingshu.search.repository.SuggestInfoRepository;
import com.atguigu.tingshu.search.runable.RebuildDistroBloomFilterRunnable;
import com.atguigu.tingshu.search.service.ItemService;
import com.atguigu.tingshu.search.thread.ExpireThread;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.atguigu.yyj.annotation.Cacheable;
import com.google.common.hash.BloomFilter;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class ItemServiceImpl implements ItemService {
    @Autowired
    private AlbumRepository albumRepository;
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private UserInfoFeignClient userInfoFeignClient;
    @Autowired
    private SuggestInfoRepository suggestInfoRepository;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ItemServiceImpl itemService;
    @Autowired
    private RBloomFilter rBloomFilter;
    static ExecutorService executorService = null;

    static {
        executorService = Executors.newFixedThreadPool(4);
    }

    HashMap<String, Object> localCache = new HashMap<>();

    BloomFilter<Long> longBloomFilter = null;

//传统方式
//todo 传统方式

    //
//  @Override
//    public void onAlbumInfos(Long albumId) {
//        //1.创建文档
//        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
//        //2.给文档赋值
//        //2.1远程查询获取专辑基本信息
//        Result<AlbumInfo> albumInfoResult=albumInfoFeignClient.getAlbumInfo(albumId);
//        AlbumInfo resultDate = albumInfoResult.getData();
//        if (null==resultDate){
//            throw new GuiguException(201,"搜索微服务调用专辑微服务获取基本信息失败");
//        }
//        albumInfoIndex.setId(resultDate.getId());
//        albumInfoIndex.setAlbumTitle(resultDate.getAlbumTitle());
//        albumInfoIndex.setAlbumIntro(resultDate.getAlbumIntro());
//        albumInfoIndex.setCoverUrl(resultDate.getCoverUrl());
//        albumInfoIndex.setIncludeTrackCount(resultDate.getIncludeTrackCount());
//        albumInfoIndex.setIsFinished(resultDate.getIsFinished().toString());
//        albumInfoIndex.setPayType(resultDate.getPayType());
//        albumInfoIndex.setCreateTime(new Date());
//        //专辑标签
//        List<AttributeValueIndex> collect = resultDate.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
//            AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
//            attributeValueIndex.setValueId(albumAttributeValue.getValueId());
//            attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
//            return attributeValueIndex;
//        }).collect(Collectors.toList());
//        albumInfoIndex.setAttributeValueIndexList(collect);
//
//        Result<BaseCategoryView> baseCategoryViewResult=albumInfoFeignClient.getAlbumInfoCategory(albumId);
//        BaseCategoryView categoryViewResultData = baseCategoryViewResult.getData();
//        if (null==categoryViewResultData){
//            throw new GuiguException(201,"搜索微服务远程查询专辑的分类信息失败");
//
//        }
//        //2.2远程查询专辑的分类信息
//        albumInfoIndex.setCategory1Id(categoryViewResultData.getCategory1Id());
//        albumInfoIndex.setCategory2Id(categoryViewResultData.getCategory2Id());
//        albumInfoIndex.setCategory3Id(categoryViewResultData.getCategory3Id());
//
//        //2.3远程查询专辑的统计信息
//        Result<AlbumStatVo> albumStatVoResult=albumInfoFeignClient.getAlbumInfoStat(albumId);
//        AlbumStatVo statVoResultData = albumStatVoResult.getData();
//        if (null==statVoResultData){
//            throw new GuiguException(201,"搜索微服务远程查询专辑的统计信息失败");
//        }
//        Integer playStatNum = statVoResultData.getPlayStatNum();
//        albumInfoIndex.setPlayStatNum(playStatNum);
//        Integer subscribeStatNum = statVoResultData.getSubscribeStatNum();
//        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
//        Integer buyStatNum = statVoResultData.getBuyStatNum();
//        albumInfoIndex.setBuyStatNum(buyStatNum);
//        Integer commentStatNum = statVoResultData.getCommentStatNum();
//        albumInfoIndex.setCommentStatNum(commentStatNum);
//
//
//        //2.4远程调用查询微服务专辑的用户信息
//        Result<UserInfoVo> userInfoResult= userInfoFeignClient.getUserInfo(resultDate.getUserId());
//        if (userInfoResult==null){
//            throw new GuiguException(201,"搜索微服务远程调用查询微服务专辑的用户信息失败");
//        }
//        UserInfoVo data = userInfoResult.getData();
//        albumInfoIndex.setAnnouncerName(data.getNickname());
//
//        //热度值
//        double nextDouble = new Random().nextDouble();
//        albumInfoIndex.setHotScore(nextDouble);
//
//        AlbumInfoIndex flag = albumRepository.save(albumInfoIndex);
//        log.info("专辑{}存储到Es中:{}", albumId,flag!=null?"success":"false");
//    }//
//todo 优化方式

    /**
     * 优化方式2 使用线程池+countdownlatch使用
     * 耗时：第一次：380ms 第二次：53ms  平均在50ms
     * 为什么第一次比较耗时：后面不怎么耗时：
     * 1、线程池来说，第一批任务进来需要创建线程（创建线程会耗时 但是耗时不多）
     * 2、远程调用的时候缓存机制。
     * 当客户端第一次远程调用服务端时，openfeign的底层会从远程的注册中心上获取对方的信息。（缓存到客户端本地）而当客户端后面每次调用服务端时，直接从客户端本地内存（Map）中获取  不在请求远端注册中心。
     * 服务发现（OpenFeign采用了缓存思想）缓存是最能提高性能。异步提高效率很低。多线程上下文切换（很耗时） Redis(单线程模型解决读写操作)、
     * Redis高性能的原因：
     * 1、Redis采用了单线程模型
     * 2、Redis客户端采用多路复用机制 select/epoll模型
     * 3、Redis内部拥有高效的数据结构（Hash 树  跳跃表 ）
     * 4、Redis中的数据都保存在内存中。
     *
     * @param albumId
     */
    @Override
    @SneakyThrows
    public void onAlbumInfos(Long albumId) {

        CountDownLatch countDownLatch = new CountDownLatch(4);//todo 不能作为成员变量,不然会出现线程不一致的问题


        //1.创建文档
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        Long startTime = System.currentTimeMillis();
        //2.给文档赋值
        Future<Long> longFuture = executorService.submit(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                //2.1远程查询获取专辑基本信息
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
                AlbumInfo resultDate = albumInfoResult.getData();
                if (null == resultDate) {
                    throw new GuiguException(201, "搜索微服务调用专辑微服务获取基本信息失败");
                }
                albumInfoIndex.setId(resultDate.getId());
                albumInfoIndex.setAlbumTitle(resultDate.getAlbumTitle());
                albumInfoIndex.setAlbumIntro(resultDate.getAlbumIntro());
                albumInfoIndex.setCoverUrl(resultDate.getCoverUrl());
                albumInfoIndex.setIncludeTrackCount(resultDate.getIncludeTrackCount());
                albumInfoIndex.setIsFinished(resultDate.getIsFinished().toString());
                albumInfoIndex.setPayType(resultDate.getPayType());
                albumInfoIndex.setCreateTime(new Date());
                //专辑标签
                List<AttributeValueIndex> collect = resultDate.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(collect);// 专辑的标签
                countDownLatch.countDown();//计数器值减一
                return resultDate.getUserId();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //2.2远程查询专辑的分类信息
                Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
                BaseCategoryView categoryViewResultData = baseCategoryViewResult.getData();
                if (null == categoryViewResultData) {
                    throw new GuiguException(201, "搜索微服务远程查询专辑的分类信息失败");

                }
                albumInfoIndex.setCategory1Id(categoryViewResultData.getCategory1Id());
                albumInfoIndex.setCategory2Id(categoryViewResultData.getCategory2Id());
                albumInfoIndex.setCategory3Id(categoryViewResultData.getCategory3Id());
                countDownLatch.countDown();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //2.3远程查询专辑的统计信息
                Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
                AlbumStatVo statVoResultData = albumStatVoResult.getData();
                if (null == statVoResultData) {
                    throw new GuiguException(201, "搜索微服务远程查询专辑的统计信息失败");
                }
                Integer playStatNum = statVoResultData.getPlayStatNum();
                albumInfoIndex.setPlayStatNum(playStatNum);
                Integer subscribeStatNum = statVoResultData.getSubscribeStatNum();
                albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
                Integer buyStatNum = statVoResultData.getBuyStatNum();
                albumInfoIndex.setBuyStatNum(buyStatNum);
                Integer commentStatNum = statVoResultData.getCommentStatNum();
                albumInfoIndex.setCommentStatNum(commentStatNum);
                //热度值
                double nextDouble = new Random().nextDouble();
                albumInfoIndex.setHotScore(nextDouble);
                countDownLatch.countDown();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // 2.2 远程查询用户微服务获取用户的基本信息
                try {
                    Long userId = longFuture.get(); // 获取上一个任务的结果 会阻塞当前线程
                    Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
                    UserInfoVo userData = userInfoVoResult.getData();
                    if (userData == null) {
                        throw new GuiguException(201, "搜索微服务调用用户微服务获取用户的基本信息失败");
                    }
                    albumInfoIndex.setAnnouncerName(userData.getNickname()); // 专辑对应主播的昵称
                    countDownLatch.countDown(); //计数器的值-1
                } catch (Exception e) {
                    log.error("当前线程阻塞期间出现了异常：{}", e.getMessage());
                }

            }
        });

        countDownLatch.await();
        AlbumInfoIndex flag = albumRepository.save(albumInfoIndex);
        Long endTime = System.currentTimeMillis();
        log.info("专辑{}存储到Es中:{}:耗时:{}", albumId, flag != null ? "success" : "false", endTime - startTime);

        //创建提示词文档当道提示索引库下
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumId.toString());
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());//未来提示的内容
        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));//专辑的汉字
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));//拼音
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));//标题首字母
        suggestInfoRepository.save(suggestIndex);
    }

    @Override
    public void offAlbumEs(Long albumId) {
        albumRepository.deleteById(albumId);
    }

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    ThreadLocal<String> deepAcquireLockTokenMap = new ThreadLocal<>();

    @Override
    @SneakyThrows
    @Cacheable(cacheKey = RedisConstant.CACHE_INFO_PREFIX + "#{#args[0]}",
            distroLockKey = RedisConstant.ALBUM_LOCK_SUFFIX + "#{#args[0]}",
            distroBloomKey = "#{#args[0]}",
            distroLockSwitch = false,
            distroBloomSwitch = false
    )
    public Map<String, Object> albumInfoDetails(Long albumId) {
        return getAlbumInfoFromDb(albumId);
    }

    @SneakyThrows
    //redis版本的分布式自旋可重入锁+递归+threadlocal+双缓存机制的缓存击穿解决方案
    private Map opsDistroCacheAndCacheHitV1(Long albumId) {
        //定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        //定义锁的key
        String localKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        Boolean lockFlag = false;
        String token = null;

        //2.查询缓存
        String jsonStrFromRedis = redisTemplate.opsForValue().get(cacheKey);

        //3.缓存命中
        if (!StringUtils.isEmpty(jsonStrFromRedis)) {
            return JSON.parseObject(jsonStrFromRedis);
        }

        //4.缓存没命中 回源
        String deepAcquireLockToken = deepAcquireLockTokenMap.get();
        if (!StringUtils.isEmpty(deepAcquireLockToken)) {
            lockFlag = true;
            token = deepAcquireLockToken;
        } else {
            token = UUID.randomUUID().toString().replace("-", "");
            lockFlag = redisTemplate.opsForValue().setIfAbsent(localKey, token, 60, TimeUnit.SECONDS);
        }
        if (lockFlag) {
            ExpireThread expireThread = new ExpireThread(redisTemplate);
            expireThread.Expire(localKey, 1l, 3l);
            try {
                //抢到锁回源
                HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
                //Thread.sleep(10000);//todo 模拟效果
                //把数据存到缓存
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
                // 4.3 释放锁
                // 释放锁执之前判断是不是自己加的 是自己加的 才删除 不是自己加的不能删除。（锁的误删解决掉）
                String script = "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long execute = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(localKey), token);
                if (execute == 0) {
                    log.error("删除失败");
                } else {
                    log.info("删除成功");
                }

                //防止内存泄露
                deepAcquireLockTokenMap.remove();
                return albumInfoFromDb;
            } finally {
                expireThread.isStop();
            }
        } else {
            // 5.1 未抢到锁  策略一：查询缓存  策略二:递归  混合使用：策略一（查询缓存）+策略二（自旋+递归）
            // 只有等锁释放了 我去递归才有意义 否则别递归 自旋抢锁
            // 通过自旋抢锁 解决无效递归 可以经可能减少栈溢出风险   cpu会很快被打满。
            //压测时间
            Thread.sleep(200);
            String result = redisTemplate.opsForValue().get(cacheKey);
            //一次缓存为了其他线程直接查缓存返回数据
            if (!StringUtils.isEmpty(result)) {
                return JSON.parseObject(result);
            }
            while (true) {
                //二次查询为了防止没抢到锁的线程自旋
                if (!StringUtils.isEmpty(redisTemplate.opsForValue().get(cacheKey))) {
                    return JSON.parseObject(redisTemplate.opsForValue().get(cacheKey));
                }
                //抢锁
                Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(localKey, token, 60, TimeUnit.SECONDS);
                if (setIfAbsent) {
                    deepAcquireLockTokenMap.set(token);
                    break;
                }
            }
        }


        return opsDistroCacheAndCacheHitV1(albumId);
    }

    @NotNull
    private HashMap<String, Object> getAlbumInfoFromDb(Long albumId) {
        HashMap<String, Object> map = new HashMap<>();
        CompletableFuture<Long> longCompletableFuture = CompletableFuture.supplyAsync(() -> {
            // 1.远程查询专辑微服务获取专辑的基本信息
            Result<AlbumInfo> albumInfo = albumInfoFeignClient.getAlbumInfo(albumId);
            AlbumInfo albumInfoData = albumInfo.getData();
            Assert.notNull(albumInfoData, "获取专辑基本信息失败");
            map.put("albumInfo", albumInfoData);  // 专辑的基本信息
            return albumInfoData.getUserId();
        }, threadPoolExecutor);

        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            // 2.远程查询专辑微服务获取专辑的统计信息
            Result<AlbumStatVo> albumInfoStat = albumInfoFeignClient.getAlbumInfoStat(albumId);
            AlbumStatVo albumInfoStatData = albumInfoStat.getData();
            Assert.notNull(albumInfoStatData, "获取专辑状态信息失败");
            map.put("albumStatVo", albumInfoStatData); // 专辑的统计信息
        }, threadPoolExecutor);

        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
            // 3.远程查询专辑微服务获取专辑的分类信息
            Result<BaseCategoryView> albumInfoCategory = albumInfoFeignClient.getAlbumInfoCategory(albumId);
            BaseCategoryView albumInfoCategoryData = albumInfoCategory.getData();
            Assert.notNull(albumInfoCategoryData, "获取专辑所在的分类信息失败");
            map.put("baseCategoryView", albumInfoCategoryData); // 专辑所在的分类信息
        }, threadPoolExecutor);


        CompletableFuture<Void> completableFuture2 = longCompletableFuture.thenAcceptAsync((userId) -> {
            // 4.远程查询用户微服务获取用户信息
            Result<UserInfoVo> userInfo = userInfoFeignClient.getUserInfo(userId);
            UserInfoVo userInfoData = userInfo.getData();
            Assert.notNull(userInfoData, "获取专辑对应的主播信息失败");
            map.put("announcer", userInfoData); // 专辑对应的主播信息
        }, threadPoolExecutor);

        //所有线程先做完
        CompletableFuture.allOf(longCompletableFuture, completableFuture, completableFuture1, completableFuture2).join();
        return map;
    }


    //排行榜预热
    @Override
    @SneakyThrows
    public void preHitRank() {
        //获取所有的一级分类id
        Result<List<Long>> c1IdListResult = albumInfoFeignClient.getCategory1IdList();
        List<Long> c1IdListResultData = c1IdListResult.getData();
        if (CollectionUtils.isEmpty(c1IdListResultData)) {
            throw new GuiguException(201, "平台没有一级分类id");
        }

        //2.遍历一级分类id集合
        for (Long c1Id : c1IdListResultData) {
            String[] dimensions = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
            for (String dimension : dimensions) {
                //b -> b.index("albuminfo").query(qb -> qb.term(tmqb -> tmqb.field("category1Id").value(c1Id))).sort(sob -> sob.field(fsb -> fsb.field(dimension).order(SortOrder.Desc))).size(10));
                SearchRequest searchRequest = SearchRequest.of(b -> b.index("albuminfo")
                        .query(qb -> qb.term(tmpb -> tmpb.field("category1Id").value(c1Id))).sort(sob -> sob.field(fsb -> fsb.field(dimension).order(SortOrder.Desc))).size(10));
                SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
                ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
                for (Hit<AlbumInfoIndex> hit : search.hits().hits()) {
                    AlbumInfoIndex source = hit.source();
                    albumInfoIndices.add(source);
                }
                String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
                // 3.存储到Redis中(String  List  Set Zset  Hash(非常合适))
                redisTemplate.opsForHash().put(bigKey, dimension, JSONObject.toJSONString(albumInfoIndices));
            }

        }

    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long c1Id, String dimension) {
        String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
        String resultStr = (String) redisTemplate.opsForHash().get(bigKey, dimension);
        if (StringUtils.isEmpty(resultStr)) {
            return new ArrayList<AlbumInfoIndexVo>();
        }
        List<AlbumInfoIndexVo> albumInfoIndexVoList = JSONObject.parseArray(resultStr, AlbumInfoIndexVo.class);
        return albumInfoIndexVoList;
    }


    public List<Long> getAlbumdIds() {
        Result<List<Long>> albumIds = albumInfoFeignClient.getAlbumIds();
        List<Long> albumIdsData = albumIds.getData();
        if (!CollectionUtils.isEmpty(albumIdsData)) {
            return albumIdsData;
        }else {
            return new ArrayList<>();
        }
    }
    @PostConstruct
    public void rebuildBloomFilter() {
        //线上 （从7天后凌晨2点钟开始执行第一次） 以后每隔7天在执行下一次
        ScheduleTaskThreadFactory instance = ScheduleTaskThreadFactory.getInstance();
        long taskBeginTime = instance.diffTime(System.currentTimeMillis());
        instance.execute(new RebuildDistroBloomFilterRunnable(redissonClient, itemService, redisTemplate), taskBeginTime, TimeUnit.MILLISECONDS);
    }



    /**
     * Redisson版本的分布式布隆+分布式锁(非阻塞的续期机制)
     * 解决缓存穿透以及缓存击穿
     * version1:redis版本（很多场景都要展开说）
     * version2:redisson版本（lock trylock  续期如何实现）
     *
     * @return
     */
    public Map opsDistroCacheAndBloomFilterRedissonVersion(Long albumId) {
        // 1.定义变量
        // 1.1 定义缓存key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;

        //2.查询分布式布隆过滤器
        boolean bloomContainFlag  = rBloomFilter.contains(albumId);
        // 2.1 布隆说没有
        if (!bloomContainFlag ){
            return null;
        }
        // 2.2 布隆说有
        //3.查询缓存
        String jsonStrFromCache  = redisTemplate.opsForValue().get(cacheKey);
        // 3.1 缓存命中
        if (!StringUtils.isEmpty(jsonStrFromCache)){
            return JSONObject.parseObject(jsonStrFromCache, Map.class);
        }
        // 3.2 缓存未命中
        // 4.获取分布式锁
        // 4.1 获取锁对象
        RLock lock = redissonClient.getLock(lockKey);
        // 4.2 加锁(解决缓存击穿)
        boolean tryLock = lock.tryLock();
        // 4.3 抢到了锁
        if (tryLock){
            try {
                // 4.3.1 回源
                HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
                // 4.3.2 同步数据  JSONObject.toJSONString(albumInfoFromDb)解决缓存穿透的固定值攻击
                Long ttl = null;
                if (albumInfoFromDb==null||albumInfoFromDb.size() == 0){
                    ttl=30l;
                }else {
                    ttl=60*24*7l;
                }
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb),ttl,TimeUnit.MINUTES);
                return albumInfoFromDb;
            } finally {
                // 4.3.3 释放锁
                lock.unlock();
            }
        }else {
            //没有抢到锁
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCache  = redisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.isEmpty(resultFromCache)){
                return JSONObject.parseObject(resultFromCache, Map.class);
            }else {
                return null;
            }
        }
    }
}

